package com.iwork.platform.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
import com.google.common.io.BaseEncoding;
import io.swagger.annotations.Api;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Copyright (c) 2016 iWork CLUB
 * Date:2017/7/3
 * Author: 2016年 <a href="heqiao@iwork.club">Qiao</a>
 * Desc:
 */
@RestController
@Api(tags = "Wechat Signature Controller")
public class WechatSignatureController {
    private final Logger log = LoggerFactory.getLogger(WechatSignatureController.class);

    private final String appid = "wx0bb9d67a5ab47497";
    private final String appSecret = "ec8762492dc6e3e605a40854d7a4ea4e";
    private final String get_access_token_url = "https://api.weixin.qq.com/cgi-bin/token";
    private final String get_ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";

    private RestTemplate restTemplate;

    @Autowired
    public WechatSignatureController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @RequestMapping(value = "/wechat/signature", method = RequestMethod.GET)
    public WechatParam wechatAuth(@RequestParam("url") String url) throws Exception {
        WechatParam wechatParam = new WechatParam();
        wechatParam.setAppId(appid);
        String nonce_str = create_nonce_str();
        wechatParam.setNonceStr(nonce_str);
        String timestamp = create_timestamp();
        wechatParam.setTimestamp(timestamp);
        wechatParam.setSignature(getSignature(nonce_str, timestamp, url));
        return wechatParam;
    }


    private final String access_token_key = "wechat_access_token";
    private final String ticket_key = "wechat_ticket";
    private final LoadingCache<String, String> cache = CacheBuilder.newBuilder()
            .maximumSize(10)
            .expireAfterWrite(7000, TimeUnit.SECONDS)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    switch (key) {
                        case access_token_key:
                            return getAccessToken();
                        case ticket_key:
                            return getTicket();
                        default:
                            return null;
                    }
                }
            });

    private String getAccessToken() {
        try {
            Map<String, Object> params = Maps.newHashMap();
            params.put("grant_type", "client_credential");
            params.put("appid", appid);
            params.put("secret", appSecret);

            ResponseEntity<AccessToken> response = restTemplate.getForEntity(get_access_token_url + prepareParam(params), AccessToken.class);
            AccessToken accessToken = response.getBody();
            log.info("obtain access token:[{}]", new ObjectMapper().writeValueAsString(accessToken));
            return accessToken.getAccess_token();
        } catch (Exception e) {
            log.error("obtain access token error. please check your appid[{}] and appsecret[{}] again. cause by:{}",
                    appid, appSecret, Throwables.getStackTraceAsString(e));
            return null;
        }
    }

    private String getTicket() {
        String accessToken = null;
        try {
            accessToken = cache.get(access_token_key);
            Map<String, Object> params = Maps.newHashMap();
            params.put("type", "jsapi");
            params.put("access_token", accessToken);
            ResponseEntity<Ticket> response = restTemplate.getForEntity(get_ticket_url + prepareParam(params), Ticket.class);
            Ticket ticket = response.getBody();
            log.info("obtain ticket:[{}]", new ObjectMapper().writeValueAsString(ticket));
            return ticket.getTicket();
        } catch (Exception e) {
            log.error("obtain ticket error. please check your access_token[{}]. cause by:{}", accessToken,
                    Throwables.getStackTraceAsString(e));
            return null;
        }
    }

    private String getSignature(String nonce_str, String timestamp, String url) throws Exception {
        Map<String, String> signMap = Maps.newHashMap();
        final String ticket = cache.get(ticket_key);
        signMap.put("jsapi_ticket", ticket);
        signMap.put("timestamp", timestamp);
        signMap.put("noncestr", nonce_str);
        signMap.put("url", url);
        List<String> list = new ArrayList<>(signMap.keySet());
        Collections.sort(list);
        Joiner equalJoiner = Joiner.on("=").skipNulls();
        String comboStr = list.stream()
                .map(item -> equalJoiner.join(item, signMap.get(item)))
                .collect(Collectors.joining("&"));
        String signature = null;
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            byte[] encryptedArray = crypt.digest(comboStr.getBytes());
            signature = BaseEncoding.base16().encode(encryptedArray).toLowerCase();
        } catch (NoSuchAlgorithmException e) {
            log.error("calculate signature error. cause by:{}", Throwables.getStackTraceAsString(e));
        }
        return signature;
    }

    private String create_nonce_str() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    private String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }

    private String prepareParam(Map<String, Object> paramMap) {
        if (paramMap == null && paramMap.isEmpty()) {
            return "";
        }
        Joiner equalJoiner = Joiner.on("=").skipNulls();
        return "?" + paramMap.entrySet().stream()
                .map(entry -> equalJoiner.join(entry.getKey(), entry.getValue()))
                .collect(Collectors.joining("&"));
    }

    @Data
    public class WechatParam implements Serializable {
        private String appId;
        private String timestamp;
        private String nonceStr;
        private String signature;
    }

    @Data
    public class AccessToken implements Serializable {
        private String access_token;
        private Integer expires_in;
    }

    @Data
    public class Ticket implements Serializable {
        private String ticket;
        private Integer expires_in;
    }
}
